// place5.js
//
// A simple jQuery plugin providing HTML5-style `placeholder` attribute support.
//
// Copyright (c) 2011 by T.J. Crowder
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.

// Overview
// ========
// HTML5 defines the `placeholder` attribute for `input` and `textarea`
// elements, which is text to be shown (usually in a dimmer color) when a
// field is empty. The placeholder usually gives an idea what the user is
// expected to put in the field.
//
// Not all browsers support `placeholder` natively yet, which is where place5
// comes in. It enables placeholders on browsers that don't support them, but
// (by default) stays out of the way of browsers that do. (You can override
// that and tell it to always take over.) place5 doesn't try to put placeholder
// text in type="password" fields, as the text would be obscured.
//
// Using place5 is dead easy: Put a `placeholder` attribute on your `input`
// and/or `textarea` elements, and then call place5's `placeholder()` function
// on a jQuery instance matching those elements.
//
// Example
// -------
//
// HTML:
//      <input type='text' name='myTextField' placeholder='Enter text here'>
//
// JavaScript:
//      jQuery("input:text, textarea:text").placeholder();
//
// CSS:
//
//      .placeholder-showing {
//          color: #ccc;
//      }
//
// Validation Note
// ---------------
//
// Because place5 has to set the `value` attribute to show the placeholder text
// in the control, it will interfere with validation. You need to call
// `$(...).placeholder("destroy");` prior to doing validation to ensure that the
// text is out of the way. (Then you can just re-enable the placeholder afterward.)
//
// API Documentation
// =================
//
// place5 adds a function to jQuery instances, and a flag to the jQuery
// object itself:
//
// place5's instance function is `placeholder`, e.g.:
//
//      jQuery("selector").placeholder();
//
// The jQuery property is `placeholderNativeSupport`, which is `true` if
// the browser supports placeholders natively, `false` if not.
//
// placeholder()
// -------------
// The function accepts zero or one arguments. If no arguments are given,
// placeholders are enabled on the matched elements using default options.
// Calling placeholder() twice on the same element is harmless, it just
// updates the options (if they're different) or is a no-op otherwise.
// The argument, if given, may be an object or a string. If it's an object,
// it's an options object which may have these properties:
//
//      override        Always use place5, even if native support is present
//      className       The class name to add to an element when showing the
//                      placeholder (default is "placeholder-showing")
//
// If the argument is a string, it's a command. Currently only one command
// is supported:
//
//      "destroy"       Remove all placeholder functionality from the matched
//                      elements. This is a no-op if the matched elements
//                      haven't had placeholders enabled.
//
// Basic Operations
// ================
//
// See if Are placeholders supported natively?
//
//      if (jQuery.placeholderNativeSupport) {
//          // Yes they are, no need to use the plugin if you don't want to
//      }
//
// Enable placeholders on all elements that have the attribute, with default
// options (see options list below):
//
//      jQuery("input[placeholder], textarea[placeholder]").placeholder();
//
// Obviously, you can use a more narrow selector as appropriate.
//
// Enable placeholders, using a custom class for when a placeholder is being
// shown:
//
//      jQuery("your_selector_here").placeholder({className: "myclass"});
//
// Enable placeholders using place5 even if the browser supports them natively;
// interferes with (overrides) the browser's own handling:
//
//      jQuery("your_selector_here").placeholder({override: true});
//
// Remove placeholder functionality from an element; useful for just before
// you do validation, since place5 sets the `value` attribute of the element:
//
//      jQuery("your_selector_here").placeholder("destroy");
//
(function($) {
    // The attribute we use to store the classname we should use when showing
    // the placeholder.
    var CLASS_DATA_ATTR_NAME = "data-placeholder-plugin-class",
        VERSION = "0.3 (alpha)";
    
    // Flag whether native support is present
    $.placeholderNativeSupport = "placeholder" in document.createElement('input');

    // Our plug-in function
    $.fn.placeholder = place5_placeholder;
    place5_placeholder.version = VERSION;
    function place5_placeholder(options) {
        var command, className, initialized, index, element;

        // If native support is available and no override given, do nothing
        if ($.placeholderNativeSupport && (typeof options !== "object" || !options.override)) {
            // Native support and no override; this is a no-op
            return this;
        }

        // Get our command if any, and our className
        command = typeof options === "string" && options;
        className = (typeof options === "object" && options.className) || "placeholder-showing";

        // Loop our matched elements
        for (index = 0; index < this.length; ++index) {
            // Get the element with wrapper
            element = this[index];

            // Skip elements of the wrong type; just do a basic check rather
            // than worrying about all the new HTML5 types
            if ((element.tagName !== "INPUT" || element.type.toLowerCase() === "password")
                && element.tagName !== "TEXTAREA") {
                continue;
            }

            // Wrap the element
            element = $(element);

            // Is it initialized?
            initialized = typeof element.attr(CLASS_DATA_ATTR_NAME) !== "undefined";

            // Command?
            if (command) {
                if (command === "destroy") {
                    // Destroy this placeholder:
                    // 1. Clear value if we placed it
                    // 2. Unbind events
                    // 3. Remove data attribute
                    if (initialized) {
                        placeholder_focusHandler.call(element[0]);
                        element.unbind("focus", placeholder_focusHandler).unbind("blur", placeholder_blurHandler);
                        element.removeAttr(CLASS_DATA_ATTR_NAME);
                    }
                }
                else {
                    throw "place5: Unknown placeholder command '" + options + "'";
                }
            }
            else {
                // Initializing or updating

                // Set/update the class we should use for placeholder
                element.attr(CLASS_DATA_ATTR_NAME, className);

                // If no previous init...
                if (!initialized) {
                    // Proactive test: They did _set_ the placeholder attribute, right?
                    /* Ditch this, unnecessarily nagging
                    if (typeof element.attr("placeholder") === 'undefined') {
                        element.removeAttr(CLASS_DATA_ATTR_NAME);
                        throw "place5: Element " + element[0].tagName + "#" + (element[0].id || "(noid)") + " has no `placeholder` attribute";
                    }
                    */

                    // Hook up our handlers
                    element.focus(placeholder_focusHandler).blur(placeholder_blurHandler);

                    // Do our initial processing
                    if (element[0] &&
                        element[0].ownerDocument && 
                        element[0].ownerDocument.activeElement === element[0]) { 
                        // It has focus
                        placeholder_focusHandler.call(element[0]);
                    }
                    else {
                        // Doesn't have focus
                        placeholder_blurHandler.call(element[0]);
                    }
                }
            }
        }

        // Done
        return this;
    }

    // Focus handler: If the value is our placeholder value, remove it and the class
    function placeholder_focusHandler() {
        var $this = $(this);

        if (this.value === ($this.attr("placeholder") || "")) {
            this.value = "";
            $this.removeClass($this.attr(CLASS_DATA_ATTR_NAME));
        }
    }

    // Blur handler: If the value is blank, show our placeholder and set our class
    function placeholder_blurHandler() {
        var $this = $(this);

        if ($.trim(this.value).length === 0) {
            this.value = $this.attr("placeholder") || "";
            $this.addClass($this.attr(CLASS_DATA_ATTR_NAME));
        }
    }

})(jQuery);